#ifndef SOLVER_H
#define SOLVER_H

#include "vector"

#include "ngl/TransformStack.h"
#include "ngl/Obj.h"
#include "ngl/BBox.h"

#include "ShaderLibrary.h"
#include "Neighbour.h"
#include "Environment.h"
#include "FluidParticle.h"

typedef std::vector<FluidParticle> ParticleVector;

/// @file Solver.h
/// @brief the main class that manages the SPH simulation
/// @author Perseedoss Rajiv
/// @version 1.0
/// @date 17.06.11
/// Revision History :
/// Initial Version 17.06.11
/// @class Solver
/// @brief creates fluid particles and perform SPH fluid simulation at every iteration

class Solver
{
public:
    /// @brief ctor
    Solver();

    /// @brief dtor
    ~Solver();

    /// @brief update the fluid state at each timestep interval
    /// @param[in,out] io_environment the environment that holds the list of obstacles
    /// @param[in] _integration the integration object for the simulation
    void updateFluid
            (
                Environment* io_environment,
                Integration* _integration
            );

    /// @brief initialise opengl drawing
    void initialiseDrawing();

    /// @brief display the particle spheres
    /// @param[in] _txStack the transformation stack used to maintain opengl states
    /// @param[in,out] io_sman the shader library
    /// @param[in] _shader the shader used to display the spheres
    void render
            (
                ngl::TransformStack _txStack,
                ShaderLibrary* io_sman,
                const std::string _shader
            );

    /// @brief inject/pumps in hose particles into the simulation
    void injectParticles();

    /// @brief return a 2d list of the position of particles, used for caching
    std::vector<std::vector<ngl::Vector> > getPositionList();

    /// @brief return the list of fluid names, used for caching
    std::vector<std::string> getNameList();

    /// @brief toggle between the fluids to determine which one will be used by the hose
    inline void toggleNextHoseableFluid()
    {
        m_currentHoseableFluid = ++m_currentHoseableFluid % m_hoseParticlePrototypeList.size();
        std::cout << "New Hoseable Fluid Id : " << m_currentHoseableFluid << "\n";
    }

    /// @brief set the current hoseable fluid id
    /// @param[in] _v the fluid id
    inline void setCurrentHoseableFluid(const int _v) { m_currentHoseableFluid = _v; }

    /// @brief returns wherether automatic fluid update is enabled
    inline bool getEnableAutoFluidUpdate() const { return m_enableAutoFluid; }

    /// @brief toggles the state of the automatic fluid update flag
    inline void toggleEnableAutoFluidUpdate() { m_enableAutoFluid ^= true; }

    /// @brief return the smoothing length of the neighbour search
    inline ngl::Real getSmoothingLength() const { return m_smoothingLength; }

    /// @brief return the flag of whether to draw hose marker
    inline bool getDrawHoseMarker() const { return m_drawHoseMarker; }

    /// @brief return the centre of the hose
    inline ngl::Vector getHoseCenter() const { return m_hoseCenter; }

    /// @brief return the velocity of the hose
    inline ngl::Vector getHoseVelocity() const { return m_hoseVelocity; }

    /// @brief return whether the hose particles wait until they first hit the boundary before they act as fluid
    inline bool getHoseWaitUntilHitBoundary() const { return m_hoseWaitUntilHitBoundary; }

    /// @brief toggles whether the hose particles wait until they first hit RBDs before they act as fluid
    inline bool getHoseWaitUntilHitRBD() const { return m_hoseWaitUntilHitRBD; }

    /// @brief set whether the hose particles wait until they first hit the boundary before they act as fluid
    /// @param[in] _v the new flag
    inline void setHoseWaitUntilHitBoundary(const bool _v) { m_hoseWaitUntilHitBoundary = _v; }

    /// @brief set whether the hose particles wait until they first hit RBDs before they act as fluid
    /// @param[in] _v the new flag
    inline void setHoseWaitUntilHitRBD(const bool _v) { m_hoseWaitUntilHitRBD = _v; }

    /// @brief set flag of whether to draw hose marker
    /// @param[in] _v the new value
    inline void setDrawHoseMarker(const bool _v) { m_drawHoseMarker = _v; }

    /// @brief set the centre of the hose
    /// @param[in] _v the new value
    inline void setHoseCenter(const ngl::Vector _v) { m_hoseCenter = _v; }

    /// @brief return the velocity of the hose
    /// @param[in] _v the new value
    inline void setHoseVelocity(const ngl::Vector _v) { m_hoseVelocity = _v; }

    /// @brief set the smoothing kernel length
    /// @param[in] _v updated smoothing length
    inline void setSmoothingLength(const ngl::Real _v)
    {
        m_smoothingLength = _v;
        m_neighbour->setCellSize(_v);
    }

    /// @brief return the list of fluid prototypes
    inline std::vector<FluidParticle> getHoseParticlePrototypeList() const { return m_hoseParticlePrototypeList; }

    /// @brief update rest density of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _restDensity the new density
    void updateRestDensityForFluid
                (
                    const int _fluidId,
                    const ngl::Real _restDensity
                );

    /// @brief update gas constant of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _gasConstant the new gas constant
    void updateGasConstantForFluid
                (
                    const int _fluidId,
                    const ngl::Real _gasConstant
                );

    /// @brief update viscosity constant of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _viscosityConstant the new viscosity constant
    void updateViscosityConstantForFluid
                (
                    const int _fluidId,
                    const ngl::Real _viscosityConstant
                );

    /// @brief update surface coefficient of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _coefficient the new coefficient
    void updateSurfaceCoefficientForFluid
                (
                    const int _fluidId,
                    const ngl::Real _coefficient
                );

    /// @brief update surface threshold of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _threshold the new threshold
    void updateSurfaceThresholdForFluid
                (
                    const int _fluidId,
                    const ngl::Real _threshold
                );

    /// @brief update interface coefficient of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _coefficient the new coefficient
    void updateInterfaceCoefficientForFluid
                (
                    const int _fluidId,
                    const ngl::Real _coefficient
                );

    /// @brief update interface threshold of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _threshold the new threshold
    void updateInterfaceThresholdForFluid
                (
                    const int _fluidId,
                    const ngl::Real _threshold
                );

    /// @brief update interface color of a fluid
    /// @param[in] _fluidId the fluid to update
    /// @param[in] _color the new color
    void updateInterfaceColorForFluid
                (
                    const int _fluidId,
                    const ngl::Real _color
                );


private:
    /// @brief list of particles making up the fluid simulation
    std::vector<FluidParticle> m_particleList;

    /// @brief the big list of neighbours of each particle
    std::vector<ParticleVector> m_neighbourForParticleList;

    /// @brief smoothing kernel length
    ngl::Real m_smoothingLength;

    /// @brief flag to control automatic movement of the fluid
    bool m_enableAutoFluid;

    /// @brief the neighbouring structure for the fluid particles
    Neighbour* m_neighbour;

    /// @brief pre-calculated part of the poly 6 kernel
    ngl::Real m_weightPoly;

    /// @brief pre-calculated part of the poly 6 gradient kernel
    ngl::Real m_weightPolyGradient;

    /// @brief pre-calculated part of the poly 6 laplacian kernel
    ngl::Real m_weightPolyLaplacian;

    /// @brief pre-calculated part of the pressure gradient kernel
    ngl::Real m_weightPressureGradient;

    /// @brief pre-calculated part of the viscosity laplacian kernel
    ngl::Real m_weightViscosityLaplacian;

    /// @brief list of particles, one per fluid that is used as a prototype when creating a hose
    std::vector<FluidParticle> m_hoseParticlePrototypeList;

    /// @brief list of particles making up the hose model
    std::vector<FluidParticle> m_hoseParticleList;

    /// @brief the current fluid id that is injected when hose is applied
    int m_currentHoseableFluid;

    /// @brief the initial position of the hose particles
    ngl::Vector m_hoseInitialCenter;

    /// @brief the current position where hose particles are injected into the scene
    ngl::Vector m_hoseCenter;

    /// @brief the velocity of hose particles
    ngl::Vector m_hoseVelocity;

    /// @brief toggles whether to draw hose marker
    bool m_drawHoseMarker;

    /// @brief toggles whether the hose particles wait until they first hit the boundary before they act as fluid
    bool m_hoseWaitUntilHitBoundary;

    /// @brief toggles whether the hose particles wait until they first hit RBDs before they act as fluid
    bool m_hoseWaitUntilHitRBD;

    /// @brief iteration counter
    int m_iteration;

private:
    /// @brief calculate the poly 6 kernel
    /// @param[in] _separationLength the separation distance between a particle and its neighbour
    ngl::Real WKernel_Poly(const ngl::Real _separationLength);

    /// @brief calculate the poly 6 gradient kernel
    /// @param[in] _separationVector the separation vector between a particle and its neighbour
    ngl::Vector WKernel_Poly_Gradient(const ngl::Vector _separationVector);

    /// @brief calculate the poly 6 laplacian kernel
    /// @param[in] _separationLength the separation distance between a particle and its neighbour
    ngl::Real WKernel_Poly_Laplacian(const ngl::Real _separationLength);

    /// @brief calculate the pressure (spiky) gradient kernel
    /// @param[in] _separationVector the separation vector between a particle and its neighbour
    ngl::Vector WKernel_Pressure_Gradient(const ngl::Vector _separationVector);

    /// @brief calculate the viscosity laplacian kernel
    /// @param[in] _separationLength the separation distance between a particle and its neighbour
    ngl::Real WKernel_Viscosity_Laplacian(const ngl::Real _separationLength);

    /// @brief normalise a vector
    /// @param[in] _vector the vector to normalise
    ngl::Vector normalise(const ngl::Vector _vector);

    /// @brief get a particle fluid id from its name
    /// @param[in] _name the fluid name
    int getFluidIdFromName(const std::string _name);


    int iteration;
};

#endif // SOLVER_H
